// Thud - a dynamic object-oriented language
// Copyright (C) 2009,2010 David H. Hovemeyer <david.hovemeyer@gmail.com>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

/*
 * VirtualMachine.cpp
 *
 *  Created on: Nov 24, 2009
 *      Author: dhovemey
 */

#include <cstdlib>
#include <memory>
#include "util/String.h"
#include "util/FileInputStream.h"
#include "util/DataInputStream.h"
#include "util/Path.h"
#include "vm/Class.h"
#include "vm/VirtualMachineException.h"
#include "vm/ClassNotFoundException.h"
#include "vm/CodeBase.h"

CodeBase::CodeBase()
{

}

CodeBase::~CodeBase()
{
	// delete classes
	for (ClassMap::iterator i = m_clsMap.begin(); i != m_clsMap.end(); i++) {
		delete (*i).second;
	}
}

void CodeBase::setThudHomeDirectory(const std::string &progName)
{
	Path progNamePath(progName);

	if (progNamePath.isAbsolute()) {
		// just strip off the directory part
		Path homeDirPath(progNamePath);
		homeDirPath.popLastComponent();
		if (homeDirPath.isDirectory()) {
			m_thudHomeDir = homeDirPath.asString();
			//printf("THUD_HOME is %s\n", m_thudHomeDir.c_str());
			return;
		}
	} else {
		// prog name path is relative, get current directory and construct absolute path
		Path curDir(Path::getCurrentDirectory());
		Path homeDirPath(curDir, progName);

		if (homeDirPath.isDirectory()) {
			m_thudHomeDir = homeDirPath.asString();
			//printf("THUD_HOME is %s\n", m_thudHomeDir.c_str());
			return;
		}
	}

	// try THUD_HOME environment variable
	char *thudHomeEnv = ::getenv("THUD_HOME");
	if (thudHomeEnv == 0) {
		throw new VirtualMachineException(L"Can't find Thud home directory, and THUD_HOME is not set");
	}
	m_thudHomeDir = thudHomeEnv;
	//printf("THUD_HOME is %s\n", m_thudHomeDir.c_str());
}

Class *CodeBase::createClass(const std::wstring &clsName)
{
	if (m_clsMap.find(clsName) != m_clsMap.end()) {
		throw VirtualMachineException(L"Duplicate class: " + clsName);
	}
	Class *cls = new Class(0, clsName);
	m_clsMap[clsName] = cls;
	return cls;
}

Class *CodeBase::getClass(const std::wstring &clsName)
{
	ClassMap::iterator i = m_clsMap.find(clsName);
	if (i == m_clsMap.end()) {
		throw VirtualMachineException(L"No such class: " + clsName);
	}
	return (*i).second;
}

void CodeBase::setClassPath(const std::string &classPathStr)
{
	String::split(classPathStr, m_classPath, ":");
}

Class *CodeBase::loadClass(const std::wstring &clsName)
{
	// see if the class has already been loaded
	ClassMap::iterator i = m_clsMap.find(clsName);
	if (i != m_clsMap.end()) {
		return (*i).second;
	}

	// Convert class name to a relative filesystem path
	std::string clsNameNarrow(String::toUtf8(clsName));
	std::vector<std::string> clsComponents;
	String::split(clsNameNarrow, clsComponents, "::");
	std::string clsNamePath;
	String::join(clsComponents, clsNamePath, "/");
	clsNamePath += ".thudc";

	// Look for class file in the system runtime class directory
	{
		Class *cls = attemptToLoadClass(clsName, m_thudHomeDir + "/runtime", clsNamePath);
		if (cls != 0) {
			return cls;
		}
	}

	// Look for class file on each directory of class path
	for (ClassPath::iterator i = m_classPath.begin(); i != m_classPath.end(); i++) {
		Class *cls = attemptToLoadClass(clsName, *i, clsNamePath);
		if (cls != 0) {
			return cls;
		}
	}

	throw ClassNotFoundException(clsName);
}

Class *CodeBase::attemptToLoadClass(const std::wstring &clsName, const std::string &dirPath, const std::string &clsNamePath)
{
	Path dir(dirPath);
	Path clsFile(dir, clsNamePath);

	//printf("Looking for %s in %s [%s]\n", clsNamePath.c_str(), dirPath.c_str(), clsFile.asString().c_str());

	if (!clsFile.isFile()) {
		// class is not in this directory
		return 0;
	}

	// Found it (hopefully)
	FileInputStream *fileIn = new FileInputStream(clsFile.asString());
	std::auto_ptr<DataInputStream> dataIn(new DataInputStream(fileIn));

	std::auto_ptr<Class> cls(new Class());
	cls->read(dataIn.get());

	// Make sure the class loaded is the one we were expecting
	if (cls->getClassName() != clsName) {
		throw ClassNotFoundException(
				clsName,
				L"File " + String::fromUtf8(clsFile.asString()) + L" contains class " + cls->getClassName());
	}

	// Got it!
	m_clsMap[clsName] = cls.get();
	return cls.release();
}
